/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include <string.h>

#include "common.h"
#include "xdrbase.h"

#define NET_INTSIZE 4

static void free_request(struct request *);
static bool_t xdr_keyvalue(XDR *, void *, struct header *, void *);

/**
 * @brief        Encode/Decode an elment of an array with offset of the array element.
 *
 * @param[in|out]    xdrs      #1: The xdr string.
 * @param[in]        data      #2: The data.
 * @param[in]        header    #3: The header.
 * @param[in]        xdr_func  #4: The xdr function to transfer the element data.
 * @param[in]        argument  #5: The external argument for the xdr_func.
 *
 * @retval       FALSE        #1: Failed
 * @retval       TRUE         #2: Success
 *
 * @note         none
 */
bool_t
gf_xdr_element(XDR *xdrs, void *data, struct header *header,
               bool_t (*xdr_func)(XDR *xdrs, void *data, struct header *header, void *argument),
               void *argument)
{
    int nextElementOffset = 0, pos;

    pos = XDR_GETPOS(xdrs);
    if (xdrs->x_op == XDR_ENCODE) {
        XDR_SETPOS(xdrs, pos + NET_INTSIZE);
    } else {
        if (!xdr_int(xdrs, &nextElementOffset)) {
            return (FALSE);
        }
    }

    if (!(*xdr_func)(xdrs, data, header, argument)) {
        return (FALSE);
    }

    if (xdrs->x_op == XDR_ENCODE) {
        nextElementOffset = XDR_GETPOS(xdrs) - pos;
        XDR_SETPOS(xdrs, pos);
        if (!xdr_int(xdrs, &nextElementOffset))
            return (FALSE);
    }

    XDR_SETPOS(xdrs, pos + nextElementOffset);

    return (TRUE);
} /* gf_xdr_element */

/**
 * @brief        Initialize the xdr header.
 *
 * @param[in|out]    header    #1: The header to be initialized.
 *
 * @retval       none
 *
 * @note         none
 */
void
gf_init_header(struct header *header)
{
    header->reqcode = 0;
    header->length    = 0;
    return;
} /* gf_init_header */

/**
 * @brief        A helper function to encode message to the xdr.
 *
 * @param[in|out]    xdrs      #1: The xdr string.
 * @param[in]        data      #2: The data.
 * @param[in]        header    #3: The header.
 * @param[in]        xdr_func  #4: The xdr function to transfer the data.
 * @param[in]        argument  #5: The external argument for the xdr_func.
 *
 * @retval       FALSE        #1: Failed
 * @retval       TRUE         #2: Success
 *
 * @note         none
 */
bool_t
gf_xdr_message(XDR *xdrs, void *data, struct header *header, struct auth *auth,
               bool_t (*xdr_func)(XDR *xdrs, void *data, struct header *header, void *argument),
               void *argument)
{
    XDR_SETPOS(xdrs, HEADER_LEN);

    if (auth) {
        if (!gf_xdr_auth(xdrs, auth, header, argument))
	    return (FALSE);
    }

    if (data) {
        if (!(*xdr_func)(xdrs, data, header, argument)) {
	    return (FALSE);
        }
    }

    int len;
    len = XDR_GETPOS(xdrs);
    header->length = len - HEADER_LEN;

    XDR_SETPOS(xdrs, 0);
    if (!gf_xdr_header(xdrs, header)) {
	return (FALSE);
    }
    XDR_SETPOS(xdrs, len);

    return (TRUE);
} /* gf_xdr_message */

/**
 * @brief        Encode/Decode the xdr header.
 *
 * @param[in|out]    xdrs      #1: The xdr string.
 * @param[in]        header    #3: The header.
 *
 * @retval       FALSE        #1: Failed
 * @retval       TRUE         #2: Success
 *
 * @note         none
 */
bool_t
gf_xdr_header(XDR *xdrs, struct header *header)
{
    unsigned int word1 = 0, word2 = 0;

    if (xdrs->x_op == XDR_ENCODE) { /* compress */
        word1 = header->reqcode;
        word1 = word1 << 16;
        word2 = header->length;
        word2 = word2 << 8;
    }

    if (!(xdr_u_int(xdrs, &word1) && xdr_u_int(xdrs, &word2))) {
        return(FALSE);
    }

    if (xdrs->x_op == XDR_DECODE) { /* uncompress */
        header->reqcode = word1 >> 16;
        header->length = word2 >> 8;
    }

    return TRUE;
} /* gf_xdr_header */

/**
 * @brief        Encode/Decode the request.
 *
 * @param[in|out]    xdrs      #1: The xdr string.
 * @param[in|out]    request   #3: The request.
 * @param[in]        header    #3: The header.
 *
 * @retval       FALSE        #1: Failed
 * @retval       TRUE         #2: Success
 *
 * @note         none
 */
bool_t
gf_xdr_request(XDR *xdrs, void *req, struct header *header, void *argument)
{
    struct request *request = (struct request *)req;
    if (xdrs->x_op == XDR_FREE) {
        free_request(request);
        return TRUE;
    }

    if (!(xdr_u_int(xdrs, &(request->number)))) {
        return FALSE;
    }

    if (request->number == 0) {
        return TRUE;
    }

    if (xdrs->x_op == XDR_DECODE) {
        request->keyvalues = (struct keyvalue *)calloc(request->number, sizeof(struct keyvalue));
        if (request->keyvalues == NULL) {
            return FALSE;
        }
    }

    unsigned int i;
    for (i = 0; i < request->number; i++) {
        if (!xdr_keyvalue(xdrs, (void *)&(request->keyvalues[i]), header, NULL)) {
            request->number = i;
            log_message(ERR, NOCLASS, "%s: xdr_keyvalue(%d) failed, key is %d.", __func__, i, request->keyvalues[i].key);
            return FALSE;
        }
    }

    return TRUE;
} /* gf_xdr_request */

unsigned int
gf_xdrsize_request(struct request *request)
{
    unsigned int i;
    unsigned int size = 0;

    size += sizeof(struct request);
    for (i = 0; i < request->number; i++) {
        size += sizeof(struct keyvalue);
        size += strlen(request->keyvalues[i].value) + 1 + sizeof(int) + XDR_OFFSET;
    }

    return size;
}

bool_t
gf_xdr_long(XDR *xdrs, void *a, struct header *header, void *argument)
{
    long *b = (long *)a;
    return (xdr_long(xdrs, b));
}

bool_t
gf_xdr_int(XDR *xdrs, void *a, struct header *header, void *argument)
{
    int *b = (int *)a;
    return (xdr_int(xdrs, b));
}

bool_t
gf_xdr_float(XDR *xdrs, void *a, struct header *header, void *argument)
{
    float *b = (float *)a;
    return (xdr_float(xdrs, b));
}

bool_t
gf_xdr_string(XDR *xdrs, void *data, struct header *header, void *argument)
{
    int pos;
    unsigned int len;

    char **astring = (char **) data;

    if (xdrs->x_op == XDR_FREE) {
        FREEUP(*astring);
        return TRUE;
    }

    bool_t needFree = FALSE;
    if (xdrs->x_op == XDR_DECODE) {

        pos = XDR_GETPOS(xdrs);

        if (!xdr_u_int(xdrs, &len)) {
            return FALSE;
        }

        if (*astring == NULL) {

            *astring = (char *)calloc(len + 1, sizeof(char));
            needFree = TRUE;

            if (*astring == NULL) {
                return FALSE;
            }
        }

        XDR_SETPOS(xdrs, pos);
    } else {
        char *str;

        if (NULL == *astring) {
            len = 0;
            str = (char *)"";
            if (!xdr_string(xdrs, &str, len + 1)) {
                return FALSE;
            }

            return TRUE;
        } else {
            len = strlen(*astring);
        }
    }

    if (!xdr_string(xdrs, astring, len + 1)) {
        if (xdrs->x_op == XDR_DECODE && needFree) {
            FREEUP(*astring);
        }
        return FALSE;
    }

    return TRUE;
}

void
gf_set_auth(struct auth *auth, char *authKind)
{
    memset(auth, 0, sizeof(struct auth));

    auth->uid = getuid();
    auth->gid = getgid();
    auth->user_name = getmy_uname();

    if (authKind == NULL || authKind[0] == '\0') {
        auth->kind = CLIENT_AUTH;
    } else if (strcmp(authKind, AUTH_PARAM_EAUTH) == 0) {
        auth->kind = CLIENT_AUTH;
    }

    return;
}

unsigned int
gf_xdrsize_auth(struct auth *auth)
{
    unsigned int size = 0;

    size += sizeof(struct auth);
    size += strlen(auth->user_name) + 1 + XDR_OFFSET;
    size += auth->k.token.len;

    return size;
}

bool_t
gf_xdr_auth(XDR *xdrs, void *clientAuth, struct header *hdr, void *argument)
{
    struct auth *auth = (struct auth *)clientAuth;

    if (xdrs->x_op == XDR_FREE) {
        if (auth == NULL) {
            return TRUE;
        }

        FREEUP(auth->user_name);
        if (auth->k.token.len > 0) {
            FREEUP(auth->k.token.data);
        }

        return TRUE;
    }

    if (!(xdr_u_int(xdrs, &auth->uid) && xdr_u_int(xdrs, &auth->gid) &&
          gf_xdr_string(xdrs, &auth->user_name, hdr, NULL))) {
        return FALSE;
    }

    if (!xdr_enum(xdrs, (int *) &auth->kind)) {
        return FALSE;
    }

    switch (auth->kind) {
    case CLIENT_AUTH:
    default:

        if (!xdr_u_int(xdrs, &auth->k.token.len))
            return FALSE;

        if (auth->k.token.len > 0) {
            if (xdrs->x_op == XDR_DECODE) {
                auth->k.token.data = (char *)calloc(auth->k.token.len, sizeof(char));
                if (auth->k.token.data == NULL) {
                    return FALSE;
                }
            }

            if (!xdr_bytes(xdrs,(char **)&auth->k.token.data,
                           (u_int *)&auth->k.token.len, auth->k.token.len)) {
                return FALSE;
            }
        }

        break;
    }

    return TRUE;
}

void
gf_xdr_free(bool_t (*xdr_func)(XDR *xdrs, void *data, struct header *header, void *argument),
            void *data)
{
    XDR xdrs;

    xdrmem_create(&xdrs, NULL, 0, XDR_FREE);
    (*xdr_func)(&xdrs, data, NULL, NULL);
    xdr_destroy(&xdrs);

    return;
}

void
free_key_value(unsigned int num, key_value_t **keyVal)
{
    unsigned int i;

    for (i=0; i<num; i++) {
        FREEUP((*keyVal)[i].value);
    }

    FREEUP(*keyVal);

    return;
}

static void
free_request(struct request *request)
{
    if (request == NULL || request->number <=0) {
        return;
    }

    free_key_value(request->number, &(request->keyvalues));
    request->number = 0;

    return;
} /* free_request */

static bool_t
xdr_keyvalue(XDR *xdrs, void *keyvalue, struct header *header, void *argument)
{
    struct keyvalue *keyval = (struct keyvalue *)keyvalue;

    if (xdrs->x_op == XDR_FREE) {
        return TRUE;
    }

    if (!(xdr_u_int(xdrs, &(keyval->key)))) {
        return FALSE;
    }

    if (!(xdr_wrapstring(xdrs, &(keyval->value)))) {
        return FALSE;
    }

    return TRUE;
} /* xdr_keyvalue */
